﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.IO;
using System.Xml;
using System.Xml.Serialization;


namespace Sublimation
{
    public class WorldProperties : DrawableGameComponent
    {
        #region Attributes
        public const int MAX_PLAYERS = 4;
        public const float AIR_FRICTION = 0.99f;
        public const int MAX_ENEMIES = 30;


        protected HighScore highScore;
        public HighScore HighScore
        {
            get { return this.highScore; }
            set { this.highScore = value; }
        }

        protected EndScreen screen;
        public EndScreen Screen
        {
            get { return screen; }
        }
        //protected int score;
        //public int Score{
        //    get{
        //        return score;
        //    }
        //    set{
        //        score = value;    
        //    }
        //}
        protected Random random;
        public Random Random
        {
            get
            {
                return random;
            }
        }
        protected Factory factory;
        public Factory Factory
        {
            get
            {
                return factory;
            }
        }
        protected SpriteBatch spriteBatch;
        public SpriteBatch SpriteBatch
        {
            get
            {
                return spriteBatch;
            }
        }
        protected GameMap map;
        public GameMap Map
        {
            get { return this.map; }
            set { this.map = value; }
        }

        protected int playerCount;
        public int PlayerCount
        {
            get { return playerCount; }
            set { playerCount =Math.Min(value, MAX_PLAYERS); }
        }

        protected Player[] players;
        public Player[] Players
        {
            get { return this.players; }
            set { this.players = value; }
        }



        protected List<EnemyUnit> enemies;
        public List<EnemyUnit> Enemies
        {
            get { return this.enemies; }
            set { enemies = value; }
        }

        public Rectangle WorldBounds()
        {
            return this.map.WorldBounds;
        }


        protected Trash trash;
        public Trash Trash
        {
            get { return this.trash; }
        }

        protected bool spawning;
        public bool Spawning
        {
            get { return spawning; }
            set { spawning = value; }
        }

        #endregion

        public virtual void GeneratePlayers()
        {
            Players = new Player[PlayerCount];
            for (int i = 0; i < PlayerCount; i++)
            {
                Players[i] = new Player(this);
            }
        }

        public void GenerateSpawnPoints()
        {
            // setup the spawn points
            spawnPoints = new SpawnPoint[MAX_SPAWN_POINTS];
            for (int i = 0; i < MAX_SPAWN_POINTS; i++) spawnPoints[i] = new SpawnPoint(this);
        }

        public void GenerateScriptedEvents()
        {
            // special events
            new ScriptedEventFirstBoss(this);
            new ScriptedEventMages(this);
            new ScriptedEventKamikazee(this);
            new ScriptedEventGodzilla(this);

            // unit unlocks
            new ScriptedEventUnlockSmallWhipGal(this);
            new ScriptedEventUnlockPriest(this);
        }

        public void StartGame()
        {
            GeneratePlayers();
            GenerateSpawnPoints();
            GenerateScriptedEvents();
        }

        protected bool gameOver = false;
        public bool isGameOver() { return gameOver; }
        public void EndGame()
        {
            // TODO: whatever needs to be done when the game is over
            gameOver = true;
            screen.State = ScreenStates.end;
            System.Diagnostics.Debug.WriteLine("Game over man, game over! Score is: ");
            highScore.Score.Add(highScore.CurrentScore);
            //save the highScores
            XmlSerializer s = new XmlSerializer(typeof(HighScore));
            TextWriter w = new StreamWriter("Resources/XML/highscore.xml");
            s.Serialize(w, highScore);
            w.Close();
        }

        public TimeSpan gameLength;
        public WorldProperties(Game game):base(game)
        {
            //Take last highScores
            XmlSerializer s = new XmlSerializer(typeof(HighScore));
            TextReader r = new StreamReader( "Resources/XML/highscore.xml");
            highScore = (HighScore)s.Deserialize( r );
            r.Close();

            spriteBatch = new SpriteBatch(game.GraphicsDevice);

            pauseTime = new TimeSpan();
            Game.Components.Add(this);
            spawning = true;
            this.Enemies = new List<EnemyUnit>();
            PlayerCount = 1; // just a default to make sure.. not really necessary
            factory = new Factory();
            random = new Random();
            trash = new Trash(this);
            screen = new EndScreen(this);
            highScore.reset();

            gameLength = new TimeSpan(0, 5, 0); // 5 mins

            // Update last for cleanup etc
            UpdateOrder = 100;
            DrawOrder = 100;
            Visible = true;
            Enabled = true;

            //Initialize();
            LoadContent();
        }

        protected int enemyCount = 0;
        public int EnemyCount
        {
            get { return this.enemyCount; }
            set { this.enemyCount = value; }
        }


        protected TimeSpan pauseTime;
        public TimeSpan PauseTime
        {
            get { return this.pauseTime; }
            set { this.pauseTime = value; }
        }
        private double lastEnemySpawnMillis = 0;
        public bool SmallCreepEnabled = true;
        public bool BigCreepEnabled = true;
        public bool SmallWhipGalEnabled = false;
        public bool PriestEnabled = false;
        public const double SMALL_CREEP_SPAWN_CHANCE = 0.52d;
        public const double BIG_CREEP_SPAWN_CHANCE = 0.08d;
        public const double SMALL_WHIP_GAL_SPAWN_CHANCE = 0.20d;
        public const double PRIEST_SPAWN_CHANCE = 0.15d;
        public const double NO_SPAWN_CHANCE = 0.05d;
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            // handle enemy spawning every half second
            // TODO: update this from my notes, this is not at all the way it will be eventually :)
            if (spawning  == true && gameTime.TotalGameTime.Subtract(pauseTime).TotalMilliseconds - lastEnemySpawnMillis >= 500)
            {
                lastEnemySpawnMillis = gameTime.TotalGameTime.Subtract(pauseTime).TotalMilliseconds;
                
                // work out the current number of enemies to have as the max
                const int TIME_UNTIL_MAX_UNITS_SECONDS = 300; // 10 mins
                int currentMaxEnemies = (int) Math.Min(
                    (MAX_ENEMIES * Math.Log((gameTime.TotalGameTime.Subtract(pauseTime).TotalSeconds * (MathHelper.E + 1) / TIME_UNTIL_MAX_UNITS_SECONDS) + 1))
                    , MAX_ENEMIES);
                
                //TODO: remove temp
                //currentMaxEnemies = MAX_ENEMIES;

                lock (Enemies)
                {
                    if(EnemyCount < currentMaxEnemies * 0.6f) // group sizes approx 40% of total amount
                    {
                        // chance the group will spawn
                        if (Random.NextDouble() > 0.6)
                        {
                            // group is spawning, which point?
                            int spawnPoint = Random.Next(MAX_SPAWN_POINTS);

                            int potentialSpawn = (currentMaxEnemies - EnemyCount);

                            double totalSpawnChance = NO_SPAWN_CHANCE;
                            if (SmallCreepEnabled) totalSpawnChance += SMALL_CREEP_SPAWN_CHANCE;
                            if (BigCreepEnabled) totalSpawnChance += BIG_CREEP_SPAWN_CHANCE;
                            if (SmallWhipGalEnabled) totalSpawnChance += SMALL_WHIP_GAL_SPAWN_CHANCE;
                            if (PriestEnabled) totalSpawnChance += PRIEST_SPAWN_CHANCE;

                            // for each unit we COULD spawn
                            for (int i = 0; i < potentialSpawn; i++)
                            {
                                double chanceToSpawn = Random.NextDouble() * totalSpawnChance;
                                double chancesChecked = 0;

                                if (chanceToSpawn < NO_SPAWN_CHANCE) continue;
                                if (SmallCreepEnabled)
                                {
                                    chancesChecked += SMALL_CREEP_SPAWN_CHANCE;
                                    if (chanceToSpawn < chancesChecked)
                                    {
                                        // spawn small creep
                                        Enemies.Add(new EnemyUnitSmallCreep(this, EnemyKind.SmallCreep, spawnPoint));
                                        continue;
                                    }
                                }
                                if (BigCreepEnabled)
                                {
                                    chancesChecked += BIG_CREEP_SPAWN_CHANCE;
                                    if (chanceToSpawn < chancesChecked)
                                    {
                                        // spawn Big Creep
                                        Enemies.Add(new EnemyUnitBigCreep(this, EnemyKind.BigCreep, spawnPoint));
                                        continue;
                                    }
                                }
                                if (SmallWhipGalEnabled)
                                {
                                    chancesChecked += SMALL_WHIP_GAL_SPAWN_CHANCE;
                                    if (chanceToSpawn < chancesChecked)
                                    {
                                        // spawn whip gal
                                         Enemies.Add(new EnemyUnitSmallWhipGal(this, EnemyKind.WhipGall, spawnPoint));
                                         continue;
                                    }
                                }
                                if (PriestEnabled)
                                {
                                    chancesChecked += PRIEST_SPAWN_CHANCE;
                                    if (chanceToSpawn < chancesChecked)
                                    {
                                        // spawn priest
                                        Enemies.Add(new EnemyUnitPriest(this, EnemyKind.Priest, spawnPoint));
                                        continue;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // update the damage text
            foreach (DrawableText text in damageText)
            {
                if (text.isAlive())
                {
                    text.ExpiryTimeRemainingMillis -= gameTime.ElapsedGameTime.TotalMilliseconds;
                    text.Scale = 0.75f + 0.25f * (float)(text.ExpiryTimeRemainingMillis / text.MaxExpiryTimeMillis);
                    text.SetAlpha((byte)(text.Color.A - text.Color.A * 0.20f * (float)(gameTime.ElapsedGameTime.TotalMilliseconds / text.MaxExpiryTimeMillis)));
                }
            }

            // update the score text
            scoreText.Message = highScore.CurrentScore.ToString("0");

            // update the time text
            TimeSpan remaining = gameLength.Subtract(gameTime.TotalGameTime.Subtract(pauseTime));
            timeText.Message = (remaining.Minutes).ToString("0") + ":" + remaining.Seconds.ToString("00");
            if (remaining.TotalSeconds <= 0)
            {
                timeText.Message = "0:00";
            }

            // update the warning text
            warningText.ExpiryTimeRemainingMillis -= gameTime.ElapsedGameTime.TotalMilliseconds;
        }

        // text
        protected Queue<DrawableText> damageText;
        protected DrawableText scoreText;
        protected DrawableText timeText;
        protected DrawableText warningText;
        protected SpriteFont gameFont;
        public SpriteFont GameFont
        {
            get { return gameFont; }
        }
        public const int MAX_ENEMY_DAMAGED_TEXT = 100;
        public const int MAX_SPAWN_POINTS = 2;
        public SpawnPoint[] spawnPoints;
        protected override void LoadContent()
        {
            base.LoadContent();

            // load the map
            Map = new GameMap(this, "ForrestMap");

            // setup the font for the game
            gameFont = Game.Content.Load<SpriteFont>("Miramonte");

            // preload some text objects for the game to use
            damageText = new Queue<DrawableText>();
            for (int i = 0; i < MAX_ENEMY_DAMAGED_TEXT; i++)
            {
                damageText.Enqueue(new DrawableText());
            }

            // the score
            scoreText = new DrawableText();
            scoreText.Scale = 1.6f;
            scoreText.Color = new Color(Color.Bisque, 140);
            ScoreTextBottomPosition = new Vector2(gameFont.MeasureString("###").X * scoreText.Scale, 768 - gameFont.MeasureString("#").Y * scoreText.Scale);
            ScoreTextTopPosition = new Vector2(1024 - gameFont.MeasureString("###").X * scoreText.Scale, gameFont.MeasureString("#").Y * scoreText.Scale);
            
            // the time
            timeText = new DrawableText();
            timeText.Scale = 1.6f;
            timeText.Color = new Color(Color.Bisque, 140);
            TimeTextBottomPosition = new Vector2(1024 - gameFont.MeasureString("0:00###").X * timeText.Scale, 768 - gameFont.MeasureString("#").Y * timeText.Scale);
            TimeTextTopPosition = new Vector2(gameFont.MeasureString("0:00###").X * timeText.Scale, gameFont.MeasureString("#").Y * timeText.Scale);

            // text to display warnings on the screen
            warningText = new DrawableText();
            warningText.Scale = 10f;
            warningText.Color = new Color(Color.Red, 200);
            WarningTextBottomPosition = new Vector2(1024 / 2, 768 * 3 / 4);
            WarningTextTopPosition = new Vector2(1024 / 2, 768 / 4);
        }

        Vector2 ScoreTextBottomPosition;
        Vector2 ScoreTextTopPosition;
        Vector2 TimeTextBottomPosition;
        Vector2 TimeTextTopPosition;
        Vector2 WarningTextBottomPosition;
        Vector2 WarningTextTopPosition;
        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            // draw text on the screen
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

            // draw the damage text
            foreach (DrawableText text in damageText)
            {
                if (text.isAlive())
                {
                    Vector2 TextOrigin = gameFont.MeasureString(text.Message) / 2;
                    spriteBatch.DrawString(gameFont, text.Message, text.Position, text.Color, text.Rotation, TextOrigin, text.Scale, SpriteEffects.None, 0.0f);
                }
            }
            
            // draw the score once on each long side of the screen
            spriteBatch.DrawString(gameFont, scoreText.Message, ScoreTextBottomPosition, scoreText.Color, scoreText.Rotation, Vector2.Zero, scoreText.Scale, SpriteEffects.None, 0.0f);
            spriteBatch.DrawString(gameFont, scoreText.Message, ScoreTextTopPosition, scoreText.Color, scoreText.Rotation + MathHelper.Pi, Vector2.Zero, scoreText.Scale, SpriteEffects.None, 0.0f);

            // draw the time once on each long side of the screen
            spriteBatch.DrawString(gameFont, timeText.Message, TimeTextBottomPosition, timeText.Color, timeText.Rotation, Vector2.Zero, timeText.Scale, SpriteEffects.None, 0.0f);
            spriteBatch.DrawString(gameFont, timeText.Message, TimeTextTopPosition, timeText.Color, timeText.Rotation + MathHelper.Pi, Vector2.Zero, timeText.Scale, SpriteEffects.None, 0.0f);

            // draw the warnings once on each long side of the screen
            if (warningText.isAlive())
            {
                Vector2 WarningTextOrigin = gameFont.MeasureString(warningText.Message) / 2;
                spriteBatch.DrawString(gameFont, warningText.Message, WarningTextBottomPosition, warningText.Color, warningText.Rotation, WarningTextOrigin, warningText.Scale, SpriteEffects.None, 0.0f);
                spriteBatch.DrawString(gameFont, warningText.Message, WarningTextTopPosition, warningText.Color, warningText.Rotation + MathHelper.Pi, WarningTextOrigin, warningText.Scale, SpriteEffects.None, 0.0f);
            }


            spriteBatch.End();
        }

        public const double DAMAGE_TEXT_EXPIRY_TIME_MILLIS = 600;
        public void DrawTextEnemyDamaged(float amount, Vector2 location)
        {
            //string message = (0 - amount).ToString("0.#");
            string message = (amount).ToString("+0.#");

            DrawableText text = damageText.Dequeue();
            text.Reset();
            text.Message = message;
            text.Position = location;
            //text.Color = new Color(Color.Red, 80);
            text.Color = new Color(Color.Honeydew, 180);
            text.MaxExpiryTimeMillis = DAMAGE_TEXT_EXPIRY_TIME_MILLIS;
            if (location.Y < Map.WorldBounds.Height / 2) text.Rotation = MathHelper.Pi;
            damageText.Enqueue(text);
        }
        
        public const float WARNING_TEXT_DESIRED_WIDTH = 1024 - 20 - 20;
        public void DrawTextWarning(string message, double expiryTime)
        {
            warningText.Message = message;
            warningText.Scale = WARNING_TEXT_DESIRED_WIDTH / gameFont.MeasureString(message).X;
            warningText.MaxExpiryTimeMillis = expiryTime;
        }

        public void DrawBloodSplatter(float damage, Unit target)
        {
            Map.DrawBloodSplatter(damage, target);
        }

        public void DrawScorchMark(Vector2 position, float scale)
        {
            Map.DrawScorchMark(position, scale);
        }
    }
}
